<?php

/**
 * @copyright Michiel Hakvoort 2010
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD
 * @package mangrove
 * @subpackage core
 * @filesource
 */

/*
 * Copyright (c) 2010 Michiel Hakvoort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

namespace mg;

include(__DIR__ . DS . 'Storage.php');
include(__DIR__ . DS . 'ArrayBackingStorage.php');
include(__DIR__ . DS . 'FileSystemStorage.php');
include(__DIR__ . DS . 'TransactionalStorage.php');
include(__DIR__ . DS . 'DBAStorage.php');
include(__DIR__ . DS . 'CacheStorage.php');

class StorageManager implements Transactional {

    private $strategies = array();

    private $storageCache = array();
    private $storageRestrict = array();

    private $storages = null;

    private $cache = null;

    /**
     *  @var StorageProvider
     */
    private $provider = null;

    private $token = null;
    /**
     *
     * @var string
     */
    private $root = null;

    /**
     * @var boolean
     */
    private $isTransactional = false;


    public function __construct(Runtime $runtime, Cache $cache = null) {
        $this->cache = $cache;

        $this->token = $runtime->getRuntimeToken();

        $this->root = $runtime->getResource('storage');

        if(false && extension_loaded('dba')) {
            $this->provider = new DBAStorageProvider($this->root, $runtime->getOS());
        } else {
            $this->provider = new FileSystemStorageProvider($this->root);
        }

        $this->storages = $this->provider->createStorage('storagemanager.storages');

        if($cache !== null) {
            $this->storages = new CacheStorage($this->storages, $cache, $this->token . md5($this->storages->getName()));
        }
    }

    /**
     * Get a storage
     *
     * @param string $storageName
     * @param boolean $restrict
     * @return Storage
     */
    public function getStorage($storageName, $restrict = false) {
        // TODO, tidy method body
        if(!preg_match('|[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*|', $storageName)) {
            throw new StorageException("Storage name must be valid PHP label");
        }

        if(isset($this->storageRestrict[$storageName])) {
            throw new StorageException("Storage '{$storageName}' is restricted for further accessing");
        }

        // Simple local cache
        if(isset($this->storageCache[$storageName])) {
            return $this->storageCache[$storageName]['storage'];
        }

        if(isset($this->storages[$storageName])) {
            $parameters = unpack('a32k', $this->storages[$storageName]);
            $key = $parameters['k'];
        } else {
            $key = md5($storageName);
            $this->storages[$storageName] = pack('a32', $key);
        }

        if($restrict) {
            $this->storageRestrict[$storageName] = true;
        }

        $storage = $this->provider->getStorage($storageName);

        if($this->isTransactional) {
            $storage = $this->transactional($storage);
            $storage->begin();
        }

        $this->storageCache[$storageName] = array('storage' => $storage);

        $this->storageCache[$storageName]['key'] = $key;

        return $storage;
    }


    /* Wrap the storage engine for intermediate caching performance
     *
     * @param Storage $storage
     * @return Storage
     */
    public function cache(Storage $storage) {
        if($this->isTransactional) {
            return $storage;
        }

        if(!isset($this->storageCache[$storage->getName()])) {
            throw new StorageException("Unknown storage '{$storage->getName()}'");
        }

        return $this->cacheNoCheck($storage);
    }

    private function cacheNoCheck(Storage $storage) {
        $key = $this->storageCache[$storage->getName()]['key'];

        if($this->cache === null) {
            return $storage;
        } else {
            return new CacheStorage($storage, $this->cache, $this->token . $key);
        }
    }


    // Create a transactional storage
    private function transactional(Storage $storage) {
        return new TransactionalStorage($storage);
    }

    private function registerStrategy($strategyName, $strategy) {
        $this->strategies[$strategyName] = $strategy;
    }

    public function begin() {
        $this->isTransactional = true;
    }

    public function commit() {
        foreach($this->storageCache as $name => $data) {
            $data['storage']->commit();
        }

        $this->isTransactional = false;
    }

    public function rollback() {
        foreach($this->storageCache as $name => $data) {
            $data['storage']->rollback();
        }

        $this->isTransactional = false;
    }
}
